#include "deferred_pipeline.h"

eTB_DeferredPipeline::~eTB_DeferredPipeline (void)
{
  shutdown ();

  // Free any GPU programs...
  if (prg_depth_debug_ != NULL) {
    //////////delete prg_depth_debug_; // LEAK!!!
    prg_depth_debug_ = NULL;

    /* TODO: Should this be done before or after deleting the program? */
    delete ps_depth_debug_;
    ps_depth_debug_ = NULL;

    delete vs_depth_debug_;
    vs_depth_debug_ = NULL;
  }
}

#include "../states.h"  // Needed for the eTB_Printf macro
#include "../console.h" // eTB_Printf

#include "../cli_parse/states.h"

#include "../../Epsilon/Epsilon3D/vector3.h"

#include "ssao.h"

const bool depth_only = false;

//#include "../prototype/shader.h"
//#include "../prototype/program.h"

extern eTB_ShaderFactory  shader_factory;
extern eTB_ProgramFactory program_factory;

extern bool LinkProgram      (eTB_Program* pProgram);
extern bool CompileAndAttach (eTB_Shader* pShader, eTB_Program* pProgram);

#define SEPARATE_POS_BUFFER
#ifdef SEPARATE_POS_BUFFER
bool __use_pos = true;
GLuint pos_buffer_;
GLuint pos_;
#else
bool __use_pos = false;
#endif

int GetNumLights (void);

//const GLenum ETB_DEFERRED_FILTER = GL_NEAREST;
const GLenum ETB_DEFERRED_FILTER = GL_LINEAR;

// 128-bit FP ?
#define ETB_USE_128_FP

// Floating-point normals?
#define ETB_DEFERRED_FP_NORMALS

// Experiment with HDRR? - 64/128-bit FP color buffer required...
#define ETB_DEFERRED_HDRR

#ifdef ETB_DEFERRED_HDRR
  eTB_Program* prg_tonemap_ = NULL;

  eTB_Shader* vs_tonemap_ = NULL;
  eTB_Shader* ps_tonemap_ = NULL;

  GLuint tonemap_sampler_;
  GLuint glowmap_sampler_;
  GLuint tonemap_exposure_;
  GLuint tonemap_bright_max_;
  GLuint tonemap_tc_offset_;

  e3dVector3 tc_offset [25];

  void InitHDRR (void)
  {
  if (prg_tonemap_ == NULL) {
   prg_tonemap_ =
     program_factory.createProgram ("HDRR Tonemapper");

   ps_tonemap_ =
     shader_factory.createShaderFromFile (_T ("shaders/hdrr_tonemap_ps.glsl"), "ps::hdrr_tonemap", eTB_Shader::Pixel);

   vs_tonemap_ =
     shader_factory.createShaderFromFile (_T ("shaders/hdrr_tonemap_vs.glsl"), "vs::hdrr_tonemap", eTB_Shader::Vertex);

   CompileAndAttach (ps_tonemap_, prg_tonemap_);
   CompileAndAttach (vs_tonemap_, prg_tonemap_);

   LinkProgram (prg_tonemap_);

   tonemap_sampler_    = prg_tonemap_->GetUniform ("hdr_in");
   glowmap_sampler_    = prg_tonemap_->GetUniform ("glow_map");
   tonemap_exposure_   = prg_tonemap_->GetUniform ("exposure");
   tonemap_bright_max_ = prg_tonemap_->GetUniform ("brightMax");
   //tonemap_tc_offset_  = prg_tonemap_->GetUniform ("tc_offset");


   e3dVector3 spread = e3dVector3 (1.0f / 32.0f,
                                   1.0f / 32.0f,
                                   0.0f);

    tc_offset[0]=spread * e3dVector3(-2.0,-2.0,0.0f);
    tc_offset[1]=spread * e3dVector3(-1.0,-2.0,0.0f);
    tc_offset[2]=spread * e3dVector3(0.0,-2.0,0.0f);
    tc_offset[3]=spread * e3dVector3(1.0,-2.0,0.0f);
    tc_offset[4]=spread * e3dVector3(2.0,-2.0,0.0f);

    tc_offset[5]=spread * e3dVector3(-2.0,-1.0,0.0f);
    tc_offset[6]=spread * e3dVector3(-1.0,-1.0,0.0f);
    tc_offset[7]=spread * e3dVector3(0.0,-1.0,0.0f);
    tc_offset[8]=spread * e3dVector3(1.0,-1.0,0.0f);
    tc_offset[9]=spread * e3dVector3(2.0,-1.0,0.0f);

    tc_offset[10]=spread * e3dVector3(-2.0,0.0,0.0f);
    tc_offset[11]=spread * e3dVector3(-1.0,0.0,0.0f);
    tc_offset[12]=spread * e3dVector3(0.0,0.0,0.0f);
    tc_offset[13]=spread * e3dVector3(1.0,0.0,0.0f);
    tc_offset[14]=spread * e3dVector3(2.0,0.0,0.0f);

    tc_offset[15]=spread * e3dVector3(-2.0,1.0,0.0f);
    tc_offset[16]=spread * e3dVector3(-1.0,1.0,0.0f);
    tc_offset[17]=spread * e3dVector3(0.0,1.0,0.0f);
    tc_offset[18]=spread * e3dVector3(1.0,1.0,0.0f);
    tc_offset[19]=spread * e3dVector3(2.0,1.0,0.0f);

    tc_offset[20]=spread * e3dVector3(-2.0,2.0,0.0f);
    tc_offset[21]=spread * e3dVector3(-1.0,2.0,0.0f);
    tc_offset[22]=spread * e3dVector3(0.0,2.0,0.0f);
    tc_offset[23]=spread * e3dVector3(1.0,2.0,0.0f);
    tc_offset[24]=spread * e3dVector3(2.0,2.0,0.0f);
  }
  }

  void StartHDRR (int samplerID)
  {
    prg_tonemap_->Bind ();
    glUniform1i  (tonemap_sampler_,    0);
    glUniform1i  (glowmap_sampler_,    1);
    glUniform1f  (tonemap_exposure_,   1.5f);
    glUniform1f  (tonemap_bright_max_, 1.0f);//1.2f + 0.9f * GetNumLights ());
    //glUniform3fv (tonemap_tc_offset_, 25, tc_offset [0].v);
  }

  void FinishHDRR (void)
  {
    prg_tonemap_->Unbind ();
  }
#else
  void InitHDRR   (void)          { }
  void StartHDRR  (int samplerID) { }
  void FinishHDRR (void)          { }
#endif

#ifdef ETB_USE_128_FP
# define ETB_DEFERRED_FP_FORMAT GL_RGB32F_ARB
#else
# define ETB_DEFERRED_FP_FORMAT GL_RGB16F_ARB
#endif

//#define SSAO
eTB_SSAO* ssao = NULL;

bool
eTB_DeferredPipeline::init (void)
{
  if (fb_ == NULL) {
    int width  = x_res_ * res_scale_;
    int height = y_res_ * res_scale_;

    int max_size;
    glGetIntegerv (GL_MAX_RENDERBUFFER_SIZE, &max_size);

    if (width > max_size)
      width = max_size;

    if (height > max_size)
      height = max_size;

    if (height < 1)
      height = 1;

    if (width < 1)
      width = 1;

    glGenFramebuffersEXT (1, &composite_fb_);
    glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, composite_fb_);

    // 08/02/2011 - We have to use a composited fb for the HDRR pipe

    /* Composited texture, we want to use a bilinear filter for this because
         this texture will be stretched to fill the entire window, regardless
           what resolution the lighting was performed at. */
    glGenTextures (1,             &composite_);
    glBindTexture (GL_TEXTURE_2D, composite_);

    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     GL_CLAMP_TO_EDGE);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     GL_CLAMP_TO_EDGE);

#define TONEMAP_FILTER GL_LINEAR_MIPMAP_LINEAR
//#define TONEMAP_FILTER GL_NEAREST_MIPMAP_LINEAR
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, TONEMAP_FILTER);

    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, TONEMAP_FILTER);
    //glTexParameteri (GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);

#ifndef ETB_DEFERRED_HDRR
    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
#else
    // Use an FP buffer to composite the shading, so we can
    //   apply tone mapping...
    glTexImage2D (GL_TEXTURE_2D, 0, ETB_DEFERRED_FP_FORMAT, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
#endif

    glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                               GL_TEXTURE_2D, composite_, 0);

    // Glow map
#ifdef ETB_DEFERRED_HDRR
    glGenTextures (1,             &glow_);
    glBindTexture (GL_TEXTURE_2D, glow_);

    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     GL_CLAMP_TO_BORDER);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     GL_CLAMP_TO_BORDER);

#define GLOWMAP_FILTER GL_LINEAR_MIPMAP_LINEAR
//#define GLOWMAP_FILTER GL_NEAREST_MIPMAP_LINEAR
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GLOWMAP_FILTER);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GLOWMAP_FILTER);
    //glTexParameteri (GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);

    glTexImage2D (GL_TEXTURE_2D, 0, ETB_DEFERRED_FP_FORMAT, width, height, 0, GL_RGB, GL_FLOAT, NULL);

    glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT,
                               GL_TEXTURE_2D, glow_, 0);

    GLuint status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);

    if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
      eTB_Printf (" >> Compositer Framebuffer Incomplete!\n");

      if (status == GL_FRAMEBUFFER_UNSUPPORTED_EXT) {
        eTB_Printf (" *** Unsupported Format ***\n");
      }
    }
#endif

    glGenFramebuffersEXT (1, &fb_);
    glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, fb_);

    glGenTextures (1,             &depth_);
    glBindTexture (GL_TEXTURE_2D, depth_);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

#if 1
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
#else
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
#endif

    glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);

    glTexImage2D (GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);

////    eTB_Printf ("Initializing Depth Texture: (%d x %d) [Max Res: %d]\n", width, height, max_size);

    glGenTextures (1,             &diffuse_);
    glBindTexture (GL_TEXTURE_2D, diffuse_);

    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     GL_CLAMP_TO_BORDER);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     GL_CLAMP_TO_BORDER);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ETB_DEFERRED_FILTER);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ETB_DEFERRED_FILTER);

    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);

    glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                               GL_TEXTURE_2D, diffuse_, 0);

    glGenTextures (1,             &normal_);
    glBindTexture (GL_TEXTURE_2D, normal_);

    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     GL_CLAMP_TO_BORDER);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     GL_CLAMP_TO_BORDER);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ETB_DEFERRED_FILTER);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ETB_DEFERRED_FILTER);

#ifndef ETB_DEFERRED_FP_NORMALS
    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
#else
    glTexImage2D (GL_TEXTURE_2D, 0, ETB_DEFERRED_FP_FORMAT, width, height, 0, GL_RGB, GL_FLOAT, NULL);
#endif

    glGenTextures (1,             &ambient_);
    glBindTexture (GL_TEXTURE_2D, ambient_);

    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     GL_CLAMP_TO_BORDER);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     GL_CLAMP_TO_BORDER);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ETB_DEFERRED_FILTER);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ETB_DEFERRED_FILTER);

    // When using HDR, the ambient lighting contribution may assume values > 1.0,
    //   which leads to a glow effect.
#ifndef ETB_DEFERRED_HDRR
    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
#else
    glTexImage2D (GL_TEXTURE_2D, 0, ETB_DEFERRED_FP_FORMAT, width, height, 0, GL_RGB, GL_FLOAT, NULL);
#endif

    glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                               GL_TEXTURE_2D, diffuse_, 0);

    glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT,
                               GL_TEXTURE_2D, normal_, 0);

    glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT2_EXT,
                               GL_TEXTURE_2D, ambient_, 0);

    glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
                               GL_TEXTURE_2D, depth_, 0);

    if (__use_pos) {
      glGenTextures (1,             &pos_);
      glBindTexture (GL_TEXTURE_2D, pos_);

      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     GL_CLAMP_TO_BORDER);
      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     GL_CLAMP_TO_BORDER);
      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ETB_DEFERRED_FILTER);
      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ETB_DEFERRED_FILTER);

      glTexImage2D (GL_TEXTURE_2D, 0, ETB_DEFERRED_FP_FORMAT, width, height, 0, GL_RGB, GL_FLOAT, NULL);

      glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT3_EXT,
                               GL_TEXTURE_2D, pos_, 0);
    }

    status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);

    if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
      eTB_Printf (" >> Framebuffer Incomplete!\n");

      if (status == GL_FRAMEBUFFER_UNSUPPORTED_EXT) {
        eTB_Printf (" *** Unsupported Format ***\n");
      }
    }

    //CheckGLErrors ();

  glBindTexture (GL_TEXTURE_2D, NULL);

  glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, NULL);

  // These only need to be compiled once...
  if (prg_depth_debug_ == NULL) {
#ifdef SSAO
   if (! ssao)
     ssao = new eTB_SSAO (this);
#endif

   prg_depth_debug_ =
     program_factory.createProgram ("Deferred Render Pipeline Depth Debug");

   ps_depth_debug_ =
     shader_factory.createShaderFromFile (_T ("shaders/debug_deferred_ps.glsl"), "ps::debug_deferred", eTB_Shader::Pixel);

   vs_depth_debug_ =
     shader_factory.createShaderFromFile (_T ("shaders/debug_deferred_vs.glsl"), "vs::debug_deferred", eTB_Shader::Vertex);

   // 08/04/2011 - There is a bug that is causing the first call to CompileAndAttach to fail.
   //                More than likely, this is due to an OpenGL error raised somewhere else.
   CompileAndAttach (vs_depth_debug_, prg_depth_debug_);
   CompileAndAttach (vs_depth_debug_, prg_depth_debug_);
   CompileAndAttach (ps_depth_debug_, prg_depth_debug_);

   LinkProgram (prg_depth_debug_);

   prg_setup_ =
     program_factory.createProgram ("Deferred Render Pipeline Setup");

   ps_setup_ =
     shader_factory.createShaderFromFile (_T ("shaders/deferred_ps.glsl"), "ps::setup_deferred", eTB_Shader::Pixel);

   vs_setup_ =
     shader_factory.createShaderFromFile (_T ("shaders/deferred_vs.glsl"), "vs::setup_deferred", eTB_Shader::Vertex);

   CompileAndAttach (ps_setup_, prg_setup_);
   CompileAndAttach (vs_setup_, prg_setup_);

   LinkProgram (prg_setup_);

   diffuse_sampler_ = prg_setup_->GetUniform ("diffuse_sampler");
   normal_sampler_  = prg_setup_->GetUniform ("normal_sampler");

   prg_composite_ =
     program_factory.createProgram ("Deferred Render Pipeline Setup");

   ps_composite_ =
     shader_factory.createShaderFromFile (_T ("shaders/deferred_composite_ps.glsl"), "ps::setup_deferred_composite", eTB_Shader::Pixel);

   vs_composite_ =
     shader_factory.createShaderFromFile (_T ("shaders/deferred_composite_vs.glsl"), "vs::setup_deferred_composite", eTB_Shader::Vertex);

   CompileAndAttach (ps_composite_, prg_composite_);
   CompileAndAttach (vs_composite_, prg_composite_);

   LinkProgram (prg_composite_);

   depth_buffer_   = prg_composite_->GetUniform ("depth_buffer");
   diffuse_buffer_ = prg_composite_->GetUniform ("diffuse_buffer");
   ambient_buffer_ = prg_composite_->GetUniform ("ambient_buffer");
   normal_buffer_  = prg_composite_->GetUniform ("normal_buffer");

   if (__use_pos) {
     pos_buffer_ = prg_composite_->GetUniform ("pos_buffer");
   }

   light_pos_      = prg_composite_->GetUniform ("light_pos");
   light_color_    = prg_composite_->GetUniform ("light_colors");
   light_count_    = prg_composite_->GetUniform ("num_lights");

   proj_mat_       = prg_composite_->GetUniform ("proj_mat");
   mv_mat_         = prg_composite_->GetUniform ("modelview_mat");

   ssao_           = prg_composite_->GetUniform ("ssao");
  }

    InitHDRR ();

    return true;
  }

  return false;
}

bool
eTB_DeferredPipeline::shutdown (void)
{
  if (fb_ != NULL) {
    finish (); // Purge all pending ops before shutting down.

    glDeleteFramebuffersEXT  (1, &composite_fb_);
    glDeleteTextures         (1, &composite_);

    glDeleteFramebuffersEXT  (1, &fb_);

    //glDeleteRenderbuffersEXT (1, &rb_depth_);
    glDeleteTextures         (1, &depth_);

    //glDeleteRenderbuffersEXT (1, &rb_diffuse_);
    glDeleteTextures         (1, &diffuse_);

    glDeleteTextures         (1, &ambient_);
    glDeleteTextures         (1, &normal_);

    if (__use_pos) {
      glDeleteTextures (1, &pos_);
      pos_ = NULL;
    }

    // Free and SET ALL FBs and RBs to NULL...

    fb_           = NULL;
    composite_fb_ = NULL;

    rb_depth_ = NULL;
    depth_    = NULL;

    rb_diffuse_ = NULL;
    diffuse_    = NULL;

    ambient_  = NULL;
    normal_   = NULL;

    composite_ = NULL;

    return true;
  }

  return false;
}

void
eTB_DeferredPipeline::resize (int x, int y)
{
  if (res_scale_ != cli_parse::states->r_deferred_scale || x_res_ != x || y_res_ != y) {
    // Shutdown and re-init, using new dimensions...
    shutdown ();

    res_scale_ = cli_parse::states->r_deferred_scale;

    x_res_ = x;
    y_res_ = y;

    init ();

    // To speed things up, only query this when the dimensions change...
    glGetFloatv (GL_PROJECTION_MATRIX, proj_);

    prg_composite_->Bind ();

    glUniformMatrix4fv (proj_mat_, 1, true, proj_);

    prg_composite_->Unbind ();
  }
}

#include "../../Epsilon/Epsilon3D/texture.h"
// Special addition, for deferred pipeline debug...
extern e3dTexture* MakeDOT3  (void);

void
eTB_DeferredPipeline::start (void)
{
  // Invalid call to start... the pipeline is already active.
  if (active_)
    return;

  // Invalid call, forgot to init (...) first, or init failure...
  if (fb_ == NULL)
    return;

  // Generate a new normal map if needed...
  e3dTexture* normal_map = MakeDOT3 ();

  prg_setup_->Bind ();

  glActiveTextureARB (GL_TEXTURE1_ARB);

  normal_map->Bind   (Epsilon3D::Texture::TexUnit1);
  normal_map->Setup  (Epsilon3D::Texture::TexUnit1);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

  glDisable (GL_ALPHA_TEST);
  glDisable (GL_BLEND);

  glActiveTextureARB (GL_TEXTURE0_ARB);

  // Always bind the diffuse map to sampler unit 0
  glUniform1i (diffuse_sampler_, 0);
  glUniform1i (normal_sampler_,  1); // Normal map on sampler unit 1

  glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, fb_);

  if (! __use_pos) {
    GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT,
                         GL_COLOR_ATTACHMENT1_EXT,
                         GL_COLOR_ATTACHMENT2_EXT };
    glDrawBuffers (3, buffers);
    glReadBuffer  (GL_NONE);
  } else {
    GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT,
                         GL_COLOR_ATTACHMENT1_EXT,
                         GL_COLOR_ATTACHMENT2_EXT,
                         GL_COLOR_ATTACHMENT3_EXT };
    glDrawBuffers (4, buffers);
    glReadBuffer  (GL_NONE);
  }

  glClearColor (1.0f, 1.0f, 1.0f, 1.0f);
  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glPushAttrib (GL_VIEWPORT);
  glViewport   (0, 0, x_res_ * res_scale_,
                      y_res_ * res_scale_);

  //if (! __use_pos) {
    glGetFloatv (GL_MODELVIEW_MATRIX, mv_);
    glUniformMatrix4fv (mv_mat_,   1, true, mv_);
  //}

  active_ = true;
}

void
eTB_DeferredPipeline::finish (void)
{
  prg_setup_->Unbind ();

  glPopAttrib (); // Viewport

  glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, NULL);

  // NOTE: Assume that setting the active texture unit to 0 undoes
  //         
  

  // Invalid call to finish... the pipeline was not active.
  if (! active_)
    return;

  active_ = false;
}

#include "../camera.h"
#include "../cli_parse/projectile.h"
#include "../../Epsilon/Epsilon3D/rendercontext.h"

// Returns the number of lights...
int
GetNumLights (void)
{
  // If r_lights is false, do not perform lighting...
  if (! cli_parse::states->r_lights)
    return 0;

  const int MAX_LIGHTS = 128;
  int num_lights = cli_parse::GetNumProjectiles () < MAX_LIGHTS ? cli_parse::GetNumProjectiles () : MAX_LIGHTS;

  return num_lights;
}

// At ~95% of the screen resolution, it is cheaper to do the composite
//   in the output framebuffer instead of stretching a lower resolution
//     surface (framebuffer) across the final viewport.
//#define ETB_DEFERRED_SCALE_BIAS 0.95f
#define ETB_DEFERRED_SCALE_BIAS 1.0f // Set to 1.0 so HDR rendering is possible...

// ^^^^ This is also an easy way to disable HDRR at the moment (08/02/2011)

void
eTB_DeferredPipeline::draw_debug (void)
{
  /* If r_deferred_mode is 1, then composite all of the buffers to form the
       final rendered scene - otherwise display each buffer separately. */
  bool composite = cli_parse::states->r_deferred_mode == 1 ? true : false;

  int x_origin;
  int y_origin;

  //eTB_RenderWindow* window = ::states->window;

  int screen_width  = x_res_;
  int screen_height = y_res_;

  int tile_width  = screen_width;
  int tile_height = screen_height;

  if (! composite) {
    // If not compositing, draw each buffer to its own
    //   portion of the viewport...  (1/4 each)
    tile_width  /= 2;
    tile_height /= 2;
  }

  for (int tex = 0; tex < 4; tex++) {
    if (composite) {
      // Do not use the other textures to composite the lighting...
      if (tex > 0)
        break;
    }

    // Do not debug the positions buffer...
    if (tex == 4)
      continue;

  glPushAttrib       (GL_ALL_ATTRIB_BITS);
  glPushClientAttrib (GL_CLIENT_ALL_ATTRIB_BITS);

  glDisable (GL_BLEND);

  if (! composite) {
    switch (tex)
    {
      /* Diffuse */
      case 0:
      {
        tile_width  = -tile_width;
        tile_height = -tile_height;

        x_origin = 0;//screen_width;
        y_origin = screen_height;

        glEnable      (GL_TEXTURE_2D);
        glBindTexture (GL_TEXTURE_2D, diffuse_);
      } break;

      /* Depth */
      case 1:
      {
        x_origin = -tile_width;
        y_origin = screen_height;

        glBindTexture (GL_TEXTURE_2D, depth_);

        prg_depth_debug_->Bind ();

        GLuint depth_sampler = prg_depth_debug_->GetUniform ("depth");

        glUniform1i (depth_sampler, 0);
      } break;

      /* Normals (or position) */
      case 2:
      {
        x_origin = 0;
        y_origin = -tile_height;

        glEnable      (GL_TEXTURE_2D);
        glBindTexture (GL_TEXTURE_2D, normal_);
      } break;

      /* Ambient */
      case 3:
      {
        x_origin = -tile_width;
        y_origin = -tile_height;

        glEnable      (GL_TEXTURE_2D);
        //glBindTexture (GL_TEXTURE_2D, pos_);
        //glBindTexture (GL_TEXTURE_2D, ambient_);
        glBindTexture (GL_TEXTURE_2D, glow_);
      } break;
    }
  } else {
    // Render to a separate framebuffer if the scale is such that performance
    //   would be improved...
    if (::cli_parse::states->r_deferred_scale <= ETB_DEFERRED_SCALE_BIAS) {
      glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, composite_fb_);

      if (::cli_parse::states->r_hdr) {
        // Normal lit scene, plus glow map.
        GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT,
                            GL_COLOR_ATTACHMENT1_EXT };
        glDrawBuffers (2, buffers);
        glReadBuffer  (GL_NONE);
      }

      //glPushAttrib (GL_VIEWPORT);
      glViewport   (0, 0, x_res_ * res_scale_,
                        y_res_ * res_scale_);

      glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    }

    prg_composite_->Bind ();

    glActiveTextureARB (GL_TEXTURE0_ARB);
    glBindTexture      (GL_TEXTURE_2D, diffuse_);

    glActiveTextureARB (GL_TEXTURE1_ARB);
    glBindTexture      (GL_TEXTURE_2D, ambient_);

    glActiveTextureARB (GL_TEXTURE2_ARB);
    glBindTexture      (GL_TEXTURE_2D, depth_);

    glActiveTextureARB (GL_TEXTURE3_ARB);
    glBindTexture      (GL_TEXTURE_2D, normal_);

    glUniform1i (diffuse_buffer_,  0);
    glUniform1i (ambient_buffer_,  1);
    glUniform1i (depth_buffer_,    2);
    glUniform1i (normal_buffer_,   3);

    if (__use_pos) {
      glActiveTextureARB (GL_TEXTURE4_ARB);
      glBindTexture      (GL_TEXTURE_2D, pos_);

      glUniform1i (pos_buffer_, 4);
    }

    ::glUniform1ui (ssao_, cli_parse::states->r_ssao ? true : false);

    e3dCamera* cam       = ::cli_parse::states->cl_player.Camera ();
    e3dVector3 light_pos = cam->GetPos ();

    const int MAX_LIGHTS = 128;

    if (! __use_pos)
      memcpy (mv_, cam->GetModelview (), sizeof (float) * 16);

    float lights_pos   [3 * MAX_LIGHTS];
    float light_colors [3 * MAX_LIGHTS];

    ZeroMemory (lights_pos,   3 * MAX_LIGHTS * sizeof (float));
    ZeroMemory (light_colors, 3 * MAX_LIGHTS * sizeof (float));

   int num_lights = GetNumLights ();

    if (num_lights > 0) {
      int x = num_lights - 1;

      for (int i = num_lights - 1; i >= 0; i--) {
        cli_parse::Projectile* projectile = cli_parse::GetProjectile (i);
        light_pos.x = projectile->origin.x;
        light_pos.y = projectile->origin.y;
        light_pos.z = projectile->origin.z;

        lights_pos [(i * 3)]     = light_pos.x;// - cam->GetPos ().x;
        lights_pos [(i * 3) + 1] = light_pos.y;// - cam->GetPos ().y;
        lights_pos [(i * 3) + 2] = light_pos.z;// - cam->GetPos ().z;

        e3dVector3& light_color = projectile->color;

        light_colors [(i * 3)]     = light_color.x;
        light_colors [(i * 3) + 1] = light_color.y;
        light_colors [(i * 3) + 2] = light_color.z;
      }
    }

    glUniform3fv (light_pos_,   num_lights, lights_pos);
    glUniform1i  (light_count_, num_lights);
    glUniform3fv (light_color_, num_lights, light_colors);

    if (! __use_pos) {
      glUniformMatrix4fv (proj_mat_, 1, true, proj_);
      glUniformMatrix4fv (mv_mat_,   1, true, mv_);
    }

    tile_width  = -tile_width;
    tile_height = -tile_height;

    x_origin = 0;//screen_width;
    y_origin = screen_height;
  }

  struct TextureVertex
  {
    int           pos   [4];
    unsigned char color [4];
    float         st    [2];
  };

  TextureVertex aTextureVertices [4];
  TextureVertex* pVert = aTextureVertices;

  float color = 1.0f;

  for (int i = 0; i < 4; i++) {
            aTextureVertices [i].pos [2] = 0;
            aTextureVertices [i].pos [3] = 1; // w = 1.0f
    (DWORD&)aTextureVertices [i].color   = 0xffffffff;//D3DCOLOR_COLORVALUE (1.0f, 1.0f, 1.0f, color);
  }

  pVert->pos [0] = x_origin;
  pVert->pos [1] = y_origin + tile_height;
  pVert->st  [0] = 0.0f;
  pVert->st  [1] = 0.0f;

  pVert++;

  pVert->pos [0] = x_origin;
  pVert->pos [1] = y_origin;
  pVert->st  [0] = 0.0f;
  pVert->st  [1] = 1.0f;

  pVert++;

  pVert->pos [0] = x_origin - tile_width;
  pVert->pos [1] = y_origin + tile_height;
  pVert->st  [0] = 1.0f;
  pVert->st  [1] = 0.0f;

  pVert++;

  pVert->pos [0] = x_origin - tile_width;
  pVert->pos [1] = y_origin;
  pVert->st  [0] = 1.0f;
  pVert->st  [1] = 1.0f;

  glMatrixMode   (GL_MODELVIEW);
  glPushMatrix   ();
  glLoadIdentity ();

  glMatrixMode   (GL_PROJECTION);
  glPushMatrix   ();
  glLoadIdentity ();

  // TODO - Eliminate the need for this by doing the viewspace
  //          transform in the vertex shader.
  glOrtho (0, screen_width, 0, screen_height, -1.0f, 1.0f);

  glDisable   (GL_CULL_FACE);
  glDisable   (GL_DEPTH_TEST);
  glFrontFace (GL_CW);

//  cli_parse::states->render_context->SetCullFaces (e3dRenderStates::False);
//  cli_parse::states->render_context->SetDepthTest (e3dRenderStates::False);

  glDisable (GL_COLOR_MATERIAL);
  glEnable  (GL_TEXTURE_2D);

  glDisableClientState (GL_COLOR_ARRAY);

  glBegin (GL_TRIANGLE_STRIP);

  glColor4f (1.0f, 1.0f, 1.0f, 1.0f);

  glTexCoord2f (aTextureVertices [0].st [0],
                aTextureVertices [0].st [1]);

  glVertex2i (aTextureVertices [0].pos [0],
              aTextureVertices [0].pos [1]);

  glTexCoord2f (aTextureVertices [1].st [0],
                aTextureVertices [1].st [1]);

  glVertex2i (aTextureVertices [1].pos [0],
              aTextureVertices [1].pos [1]);

  glTexCoord2f (aTextureVertices [2].st [0],
                aTextureVertices [2].st [1]);

  glVertex2i (aTextureVertices [2].pos [0],
              aTextureVertices [2].pos [1]);

  glTexCoord2f (aTextureVertices [3].st [0],
                aTextureVertices [3].st [1]);

  glVertex2i (aTextureVertices [3].pos [0],
              aTextureVertices [3].pos [1]);

  glEnd ();

//  glDisable (GL_BLEND);

  //glMatrixMode (GL_PROJECTION); // Redundant
  glPopMatrix  ();

  glMatrixMode (GL_MODELVIEW);
  glPopMatrix  ();

  glPopClientAttrib ();
  glPopAttrib       ();

  if (tex == 1) {
    prg_depth_debug_->Unbind ();
  }

  if (composite) {
    prg_composite_->Unbind ();

    // Render to a separate framebuffer if the scale is such that performance
    //   would be improved...
    if (::cli_parse::states->r_deferred_scale <= ETB_DEFERRED_SCALE_BIAS) {
      glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, NULL);

      //glPopAttrib ();

      //
      // XXX: 07/28/2011
      //
      //  This is evil, but there is an attrib stack overflow somewhere, it appears.
      //
      glPushAttrib   (GL_ENABLE_BIT);

      glMatrixMode   (GL_MODELVIEW);
      glPushMatrix   ();
      glLoadIdentity ();

      glMatrixMode   (GL_PROJECTION);
      glPushMatrix   ();
      glLoadIdentity ();

      glOrtho (0, screen_width, 0, screen_height, -1.0f, 1.0f);

      glDisable   (GL_CULL_FACE);
      glDisable   (GL_DEPTH_TEST);

  /*
      glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
      glDepthMask (GL_TRUE);
  */

      //cli_parse::states->render_context->SetCullFaces (e3dRenderStates::False);
     //cli_parse::states->render_context->SetDepthTest (e3dRenderStates::False);

    //  cli_parse::states->render_context->SetCullFaces (e3dRenderStates::False);
    //  cli_parse::states->render_context->SetDepthTest (e3dRenderStates::False);

      glDisable (GL_COLOR_MATERIAL);
      glDisable (GL_BLEND);
      glDisable (GL_ALPHA_TEST);
      glBindTexture    (GL_TEXTURE_2D, composite_);
      glEnable         (GL_TEXTURE_2D);
      glGenerateMipmap (GL_TEXTURE_2D);  //Generate mipmaps now!!!


      // Use the composite texture to apply HDRR (if it is an FP buffer...)
      if (cli_parse::states->r_hdr) {
#ifndef SSAO
        glActiveTextureARB (GL_TEXTURE1_ARB);
        glBindTexture    (GL_TEXTURE_2D, glow_);
        glEnable         (GL_TEXTURE_2D);
        glGenerateMipmap (GL_TEXTURE_2D);  //Generate mipmaps now!!!

        // In lieu of a better state management system, assume setting the
        //   active texture unit to 0 will restore expected behaviour...
        glActiveTextureARB (GL_TEXTURE0_ARB);

        StartHDRR (0);
#else
        ssao->Start ();
#endif
      }

      glDisableClientState (GL_COLOR_ARRAY);

      glBegin (GL_TRIANGLE_STRIP);

      glColor4f (1.0f, 1.0f, 1.0f, 1.0f);

      glTexCoord2f (aTextureVertices [0].st [0],
                    aTextureVertices [0].st [1]);

      glVertex2i (aTextureVertices [0].pos [0],
                  aTextureVertices [0].pos [1]);

      glTexCoord2f (aTextureVertices [1].st [0],
                    aTextureVertices [1].st [1]);

      glVertex2i (aTextureVertices [1].pos [0],
                  aTextureVertices [1].pos [1]);

      glTexCoord2f (aTextureVertices [2].st [0],
                    aTextureVertices [2].st [1]);

      glVertex2i (aTextureVertices [2].pos [0],
                  aTextureVertices [2].pos [1]);

      glTexCoord2f (aTextureVertices [3].st [0],
                    aTextureVertices [3].st [1]);

      glVertex2i (aTextureVertices [3].pos [0],
                  aTextureVertices [3].pos [1]);

      glEnd ();

//  glDisable (GL_BLEND);

      //glMatrixMode (GL_PROJECTION); // Redundant
     glPopMatrix  ();

      glMatrixMode (GL_MODELVIEW);
      glPopMatrix  ();

      if (cli_parse::states->r_hdr) {
#ifndef SSAO
        FinishHDRR ();
#else
        ssao->Finish ();
#endif
      }

      glPopClientAttrib ();
      glPopAttrib       ();
    }
  }
  }
}

#include "../window.h"
void
DrawDeferredStats (void)
{
  eTB_RenderWindow* window = ::states->window;

  if (cli_parse::states->r_deferred_mode == 1) {
    window->color_printf (0xffffffff, 64, 32, _T ("Deferred Pipeline: %d lights (HDR: %s)"), GetNumLights (), cli_parse::states->r_hdr ? "On" : "Off");
  } else if (cli_parse::states->r_deferred_mode == 2) {
    window->color_printf (0xffffffff, 64, 32, _T ("Deferred <G-Buffer>: [Diff + Depth + Norm + Amb]"));
  }
}